/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: posix_thread.c
 *
 * Purpose: implementation thread with posix API
 *
 * Developer:
 *   wen.gu , 2022-01-21
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "config.h"
#if defined(HAS_POSIX_THREAD) && (HAS_POSIX_THREAD) 
#include "cbus/core/cbus_thread.h"

#include <pthread.h>
#include <errno.h>
#include <sched.h>
#include <time.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>

#include "cbus/core/cbus_log.h"


/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define THD_NAME_MAX_LEN 16

#define THD_GET_HANDLE(cbus_thd) (((cbus_thd) != NULL) ? ((struct _cbus_thread*)cbus_thd)->thd : pthread_self())
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef enum _cbus_thread_state
{
    AVP_THD_READY = 0,
    AVP_THD_RUNNING,
    AVP_THD_STOP,
}cbus_thread_state;

struct _cbus_thread
{
    GBOL joinable;
    enum _cbus_thread_state state;
    GPTR opaque;
    cbus_thread_func func;
    pthread_t thd;
    pthread_mutex_t lock;
    pthread_cond_t cond;
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static inline void* cbus_thread_runnable(void* opaque)
{
    struct _cbus_thread* thd = (struct _cbus_thread*)opaque;

    pthread_mutex_lock(&thd->lock);
    while ((thd->state == AVP_THD_READY) && (thd->state != AVP_THD_STOP))
    {
        pthread_cond_wait(&thd->cond, &thd->lock);
    }
    pthread_mutex_unlock(&thd->lock); 

    if ((thd->state == AVP_THD_RUNNING) && (thd->func != NULL))
    {
        thd->func(thd->opaque);
    }

    if (thd->joinable == GFALSE) /** detach mode, need release resource by thread himself */
    {
        pthread_mutex_destroy(&thd->lock);
        pthread_cond_destroy(&thd->cond);
        
        free(thd);        
    }

    return NULL;
}

static void cbus_thread_set_state(struct _cbus_thread* cbus_thd, cbus_thread_state state)
{
    pthread_mutex_lock(&cbus_thd->lock);
    cbus_thd->state = state;
    pthread_cond_broadcast(&cbus_thd->cond);
    pthread_mutex_unlock(&cbus_thd->lock);    
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
/**
 * @brief create a thread instance with the thread func and a user opaque
 * 
 * @param func [in] the thread running function
 * @param opaque [in] a user private argument for thread function
 * @return cbus_thread_t  the thread instance, if failed, return NULL
 */
cbus_thread_t cbus_thread_create(cbus_thread_func func, GPTR opaque)
{
    if (func == NULL )
    {
        LOGE("must set a thread runnable function\n");
        return NULL;
    }

    struct _cbus_thread* thd = (pthread_t*)malloc(sizeof(struct _cbus_thread));
    if (thd == NULL)
    {
        LOGE("alloc thread instance failed\n");
        return NULL;
    }
    
    pthread_attr_t type;

  /* Set the thread attributes */
    if (pthread_attr_init(&type) != 0) 
    {
        LOGE("Couldn't initialize pthread attributes");
        free(thd);
        return NULL;
    }

    if (pthread_mutex_init(&thd->lock, NULL) != 0)
    {
        LOGE("create mutex lock for thread failed(%s)\n", strerror(errno));
        free(thd);
        return NULL;
    }

    if (pthread_cond_init(&thd->cond, NULL) != 0)
    {
        LOGE("create condition variable for thread failed(%s)\n", strerror(errno));
        pthread_mutex_destroy(&thd->lock);
        free(thd);
        return NULL;
    }

    pthread_attr_setdetachstate(&type, PTHREAD_CREATE_JOINABLE);

    thd->state = AVP_THD_READY;

    int res = pthread_create(&thd->thd, &type, cbus_thread_runnable, (void*)thd);

    if (res != 0)
    {
        LOGE("create pthread thread failed(%s)\n", strerror(errno));
        pthread_mutex_destroy(&thd->lock);
        pthread_cond_destroy(&thd->cond);
        free(thd);
        return NULL;
    }

    thd->func = func;
    thd->opaque = opaque;
    thd->joinable = GTRUE;

    return (cbus_thread_t)thd;
}

/**
 * @brief join(wait) and destroy a thread instance
 * 
 * @param thd [in] the thread instance
 * @return void
 * @note this API maybe block forever if cbus_thread_func not return
 */
GErrc cbus_thread_join(cbus_thread_t thd)
{
    if (!thd)
    {
        return G_ErrBadParameter;
    }

    struct _cbus_thread* cbus_thd = (struct _cbus_thread*)thd;

    if (cbus_thd->joinable == GTRUE)
    {        
        cbus_thread_set_state(cbus_thd, AVP_THD_STOP);

        pthread_join(cbus_thd->thd, NULL);
        pthread_mutex_destroy(&cbus_thd->lock);
        pthread_cond_destroy(&cbus_thd->cond);
        
        free(thd);
        return G_OK;
    }

    return G_ErrInvalidStatus;
}

GErrc cbus_thread_detach(cbus_thread_t thd)
{
    if (!thd)
    {
        return G_ErrBadParameter;
    }

    struct _cbus_thread* cbus_thd = (struct _cbus_thread*)thd;

    if (cbus_thd->joinable == GFALSE)
    {
        return G_ErrInvalidStatus;
    }

    pthread_detach(cbus_thd->thd);
    cbus_thd->joinable = GFALSE;

    return G_OK;
}

/**
 * @brief start to run the given thread, which must have been initialized
 * 
 * @param thd [in] the thread instance
 * @return void 
 */
void cbus_thread_run(cbus_thread_t thd)
{
    if (thd)
    {
        cbus_thread_set_state((struct _cbus_thread*)thd, AVP_THD_RUNNING);
    }
}

/**
 * @brief set a short name for the thread
 * 
 * @param thd  [in] the thread instance, if this arg is NULL, then is set the name for current thread which called this PAI
 * @param name [in] the name of thread
 * @return void
 */
void cbus_thread_set_name(cbus_thread_t thd, const GSTR name)
{
    if (name == NULL)
    {
        return ;
    }
#if defined(HAS_POSIX_THREAD_SET_NAME_API) && HAS_POSIX_THREAD_SET_NAME_API
    pthread_t pthd = THD_GET_HANDLE(thd);
    int ret = pthread_setname_np(pthd, name);
    if (ret != 0)
    {
        LOGE("set thread name: %s, failed:%s\n", name, strerror(errno));
    }

#else
    LOGW("set thread name: %s, will not take effect\n", name);
#endif
}

/**
 * @brief get the name of the thread
 * 
 * @param thd [in] the thread instance, if this arg is NULL, then is get the name of current thread which called this API
 * @return const GPTR  the name of the thread, if failed, then return empty string ' return ""; '
 * @note   the return c string must do free after use. 
 */
const GSTR cbus_thread_name(cbus_thread_t thd)
{
#if defined(HAS_POSIX_THREAD_GET_NAME_API) && HAS_POSIX_THREAD_GET_NAME_API
    GSTR thd_name = (GSTR)malloc(THD_NAME_MAX_LEN);
    if (thd_name == NULL)
    {
        LOGE("alloc the bufffer for thread name failed");
        return NULL;
    }

    pthread_t pthd = THD_GET_HANDLE(thd);
    int ret = pthread_getname_np(pthd, thd_name, THD_NAME_MAX_LEN);
    if (ret != 0)
    {
        LOGE("get thread name failed:%s\n", strerror(errno));
        free(thd_name);
        return NULL;
    }

    return thd_name;
#else
    LOGW("has no get name API, so this call will not take effect, just return empty string \n");
    return NULL;
#endif
}


/**
 * @brief set a priority for the thread
 * 
 * @param thd      [in] the thread instance,if this arg is NULL, then is set priority for current thread which called this PAI
 * @param priority [in] the priority of thread
 * @return void
 */
void cbus_thread_set_priority(cbus_thread_t thd, GS32 priority)
{
#if defined(_WIN32) /** for windows */
    GS32 policy = SCHED_OTHER;
#else /** other */
    GS32 policy = SCHED_RR;
#endif
    struct sched_param sch_param = {0};
  
    sch_param.sched_priority = priority;  

    pthread_t pthd = THD_GET_HANDLE(thd);

    int ret = pthread_setschedparam(pthd, policy, &sch_param);

    if (ret != 0)
    {
        LOGE("set sched param failed(%s)\n", strerror(errno));
    }
}

/**
 * @brief get the priority of the thread
 * 
 * @param thd  [in] the thread instance, if this arg is NULL, then is get the priority of current thread which called this API
 * @param priority [io] the priority of thread
 * @return GErrc  success: G_OK, else error code @see GErrc
 */
GErrc cbus_thread_priority(cbus_thread_t thd, GS32* priority)
{
    if (priority == NULL)
    {
        return G_ErrBadParameter;
    }

    struct sched_param sch_param = {0};
    int policy = 0;
    pthread_t pthd = THD_GET_HANDLE(thd);
    int ret = pthread_getschedparam(pthd, &policy, &sch_param);

    if (ret != 0)
    {
        LOGE("get sched param failed(%s)\n", strerror(errno));
        return G_ErrUndefined;
    }

    *priority = sch_param.sched_priority;

    return G_OK;
}

/**
 * @brief check the thread is current running thread
 * 
 * @param thd [in] the thread instance, if this arg is NULL, then always return GTRUE
 * @return GBOL GTRUE: the thread instance is current running thread, GFALSE: not current running thread
 */
GBOL cbus_thread_is_self(cbus_thread_t thd)
{
    pthread_t pthd = THD_GET_HANDLE(thd);

    return pthread_self() == pthd; /** todo refine me? return GTRUE or GFALSE by hand */
}

/**
 * @brief do sleep in milli-second unit
 * 
 * @param time_ms [in] how long to sleep
 * @return void
 */
void cbus_thread_sleep(GU32 time_ms)
{
    struct timespec ts = {time_ms / 1000, (time_ms % 1000) * 1000000 };
    while ((-1 == nanosleep(&ts, &ts)) && (EINTR == errno));
}

#endif /** defined HAS_POSIX_THREAD */
